﻿using System;
using System.Collections.Generic;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using GeoSketch2.Core;
using System.Xml;
using System.Drawing;
using System.Reflection;

namespace GeoSketch2.Archaeo
{
    /// <summary>
    /// The base class for excavation zone, it could be the whole site, a square, or just a unit
    /// </summary>
    public class ExcavationArea
    {
        #region XML
#pragma warning disable 1591
        public const string XML_SpecificInfos = "SpecificInfos";
        public const string XML_BaseInfos = "BaseInfos";
        public const string XML_Name = "Name";
        public const string XML_CreationDate = "CreationDate";
        public const string XML_Bounds = "Bounds";
        public const string XML_Archaeologist = "Archaeologist";
        public const string XML_AvailableLayers = "AvailableLayers";
        public const string XML_ExcavationArea = "ExcavationArea";
        public const string XML_ChildrenExcavationAreas = "ChildExcavationAreas";
        public const string XML_ParentExcavationArea = "ParentExcavationArea";
#pragma warning restore 1591
        #endregion

        #region Fields
        /// <summary>
        /// The available layers from this area
        /// </summary>
        public List<Layer> AvailableLayers;

        /// <summary>
        /// The bounds of the area in object coordinates, Y-axis upside
        /// </summary>
        public Point2D64D[] Bounds;

        /// <summary>
        /// The name of the excavation area
        /// </summary>
        public string Name;

        /// <summary>
        /// The time of creation
        /// </summary>
        public DateTime CreationTime;

        /// <summary>
        /// The name of the archaeologist
        /// </summary>
        public string ArchaeologistName;

        ExcavationArea _ParentExcavationArea;

        public List<ExcavationArea> ChildrenExcavationAreas;

        ArchaeoProject _Project;

        SurveyLogBook _SurveyLogBook;

        BaseSoilSheet _SoilSheet;
        #endregion

        #region Constructor
        /// <summary>
        /// The base class for excavation zone, it could be the whole site, a square, or just a unit
        /// </summary>
        /// <param name="Bounds">The bounds of the area in object coordinates, Y-axis upside</param>
        public ExcavationArea(Point2D64D[] Bounds, ExcavationArea ParentExcavationArea)
        :this(ParentExcavationArea.Name+"Subdiv"+ParentExcavationArea.ChildrenExcavationAreas.Count,ParentExcavationArea.ArchaeologistName,DateTime.Now,Bounds,new List<Layer>(),ParentExcavationArea)
        {
            this.AvailableLayers.AddRange(ParentExcavationArea.AvailableLayers);
        }

        /// <summary>
        /// The base class for excavation zone, it could be the whole site, a square, or just a unit
        /// </summary>
        /// <param name="Name">The name of the excavation area</param>
        /// <param name="ArchaeologistName">The name of the archaeologist</param>
        /// <param name="CreationTime">The time of creation</param>
        /// <param name="Bounds">The bounds of the area in object coordinates, Y-axis upside</param>
        /// <param name="AvailableLayers">The available layers from this area</param>
        public ExcavationArea(string Name, string ArchaeologistName, DateTime CreationTime, Point2D64D[] Bounds, List<Layer> AvailableLayers, ExcavationArea ParentExcavationArea)
        {
            this.Name = Name;
            this.ArchaeologistName = ArchaeologistName;
            this.CreationTime = CreationTime;
            this.Bounds = Bounds;
            this.AvailableLayers = AvailableLayers;
            this.ParentExcavationArea = ParentExcavationArea;
            this.ChildrenExcavationAreas = new List<ExcavationArea>();
            this.SurveyLogBook = new SurveyLogBook(this);
            this.SoilSheet = Project.SoilSheetModel.GetNew();
            
            Layer DefaultLayer=new Layer(this.Name+"DefaultLayer",Vars.Setup.StandardObjects.ExcavationAreas.ExcavationSquare.DefaultLayerColor,this.Project.DefaultLineType,true);
            AvailableLayers.Add(DefaultLayer);
            Project.ProjectLayers.Add(DefaultLayer);
        }

        /// <summary>
        /// The base class for excavation zone, it could be the whole site, a square, or just a unit
        /// </summary>
        /// <param name="Name">The name of the excavation area</param>
        /// <param name="ArchaeologistName">The name of the archaeologist</param>
        /// <param name="CreationTime">The time of creation</param>
        /// <param name="Bounds">The bounds of the area in object coordinates, Y-axis upside</param>
        /// <param name="AvailableLayers">The available layers from this area</param>
        public ExcavationArea(string Name, string ArchaeologistName, DateTime CreationTime, Point2D64D[] Bounds, List<Layer> AvailableLayers, ExcavationArea ParentExcavationArea,ArchaeoProject Project)
        {
            this._Project = Project;
            this.Name = Name;
            this.ArchaeologistName = ArchaeologistName;
            this.CreationTime = CreationTime;
            this.Bounds = Bounds;
            this.AvailableLayers = AvailableLayers;
            this.ParentExcavationArea = ParentExcavationArea;
            this.ChildrenExcavationAreas = new List<ExcavationArea>();
            this.SurveyLogBook = new SurveyLogBook(this);
            this.SoilSheet = Project.SoilSheetModel.GetNew();
        }

        /// <summary>
        /// The base class for excavation zone, it could be the whole site, a square, or just a unit
        /// </summary>
        /// <param name="Xr">The XmlReader to read from</param>
        /// <param name="Site">The excavation site containing this area</param>
        public ExcavationArea(XmlReader Xr, ArchaeoProject Project)
        {
            #region Name
            Xr.ReadToFollowing(XML_Name);
            this.Name = Xr.ReadString();
            #endregion

            #region Creation Date
            Xr.ReadToFollowing(XML_CreationDate);
            this.CreationTime = DateTime.FromBinary(Convert.ToInt64(Xr.ReadString(), Core.Vars.Setup.GeoSketch.NumberFormat));
            #endregion

            #region Archaeologist Name
            Xr.ReadToFollowing(XML_Archaeologist);
            this.ArchaeologistName = Xr.ReadString();
            #endregion

            #region Bounds
            List<Point2D64D> BoundsList = new List<Point2D64D>();

            Xr.ReadToFollowing(XML_Bounds);
            Xr.ReadToFollowing(typeof(Point2D64D).FullName);
            while (Xr.NodeType != XmlNodeType.EndElement && Xr.Name != XML_Bounds)
            {
                BoundsList.Add(new Point2D64D(Xr));
                Xr.ReadEndElement();
            }
            this.Bounds = BoundsList.ToArray();
            #endregion

            #region Area Available Layers
            this.AvailableLayers = new List<Layer>();
            string LayerName;

            Xr.ReadToFollowing(XML_AvailableLayers);
            Xr.ReadToFollowing(Layer.XML_Name);
            while (Xr.NodeType != XmlNodeType.EndElement && Xr.Name != XML_AvailableLayers)
            {
                LayerName = Xr.ReadString();
                this.AvailableLayers.Add(Project.ProjectLayers.Find(delegate(Layer l) { return LayerName == l.Name; }));
                Xr.ReadEndElement();
            }
            #endregion

            #region Parent Area
            Xr.ReadToFollowing(XML_ParentExcavationArea);
            this.ParentExcavationArea = Project.GetExcavationAreaFromName(Xr.ReadString());

            if (this.ParentExcavationArea == null)
            {
                this._Project = Project;
                this._Project.MainSite = this;
            }
            #endregion

            #region Soil Description Sheet
            Xr.ReadToFollowing(BaseSoilSheet.XML_Template);
            this.SoilSheet = (BaseSoilSheet)Type.GetType(Xr.ReadString()).InvokeMember(null, BindingFlags.CreateInstance, null, null, new object[] { Xr }); 
            #endregion

            #region Children Area
            this.ChildrenExcavationAreas = new List<ExcavationArea>();

            Xr.ReadToFollowing(XML_ChildrenExcavationAreas);
            if (!Xr.IsEmptyElement)
            {
                Xr.ReadToFollowing(this.GetType().FullName);
                bool test = !(Xr.NodeType == XmlNodeType.EndElement && Xr.Name == XML_ChildrenExcavationAreas);
                while (!(Xr.NodeType == XmlNodeType.EndElement && Xr.Name == XML_ChildrenExcavationAreas))
                {
                    new ExcavationArea(Xr, Project);
                    Xr.ReadEndElement();
                }
                Xr.ReadEndElement();
            }
            else
            {
                Xr.Read();
            }
            
            #endregion
        }
        #endregion

        #region Methods
        /// <summary>
        /// The methods to sort layers according to their display order
        /// </summary>
        public void SortLayers()
        {
            AvailableLayers.Sort(Layer.CompareLayerDisplayOrderAsc);
        }

        /// <summary>
        /// True if the point is inside the area
        /// </summary>
        public bool IsPointInsideArea(Point2D64D Point)
        {
            GraphicsPath gp = Bounds.ToGraphicsPath();
            return gp.IsVisible(Point);
        }

        /// <summary>
        /// The method used if the area has something to draw, which is not a geo object
        /// </summary>
        /// <param name="Viewport">The viewport the area will draw to</param>
        public void Draw(Viewport Viewport)
        {
            foreach (ExcavationArea a in ChildrenExcavationAreas)
            {
                PointF[] bounds_screen_coordinates = Viewport.Object2ImageTransformation.TransformF(a.Bounds);
                GraphicsPath path = new GraphicsPath(bounds_screen_coordinates, Tools.GetPathPointsType(bounds_screen_coordinates.Length));
                Viewport.Graphic.DrawPath(Vars.Setup.StandardObjects.ExcavationAreas.ExcavationSite.ExcavationSquaresDisplayPen, path);

                Point2D64D BoundsCentroid = a.Bounds.GetCentroid();
                BoundsCentroid = Viewport.Object2ImageTransformation.Transform(BoundsCentroid);
                SizeF StringSize = Viewport.Graphic.MeasureString(a.Name, Vars.Setup.StandardObjects.ExcavationAreas.ExcavationSite.NameFontdisplay);
                BoundsCentroid.X -= StringSize.Width / 2;
                BoundsCentroid.Y -= StringSize.Height / 2;
                Viewport.Graphic.DrawString(a.Name, Vars.Setup.StandardObjects.ExcavationAreas.ExcavationSite.NameFontdisplay, Vars.Setup.StandardObjects.ExcavationAreas.ExcavationSite.ExcavationSquaresDisplayPen.Brush, BoundsCentroid);
            }
        }

        /// <summary>
        /// Writes the object to a file
        /// </summary>
        /// <param name="Xw">The XmlWriter to write to</param>
        public void ToFile(XmlWriter Xw)
        {
            Xw.WriteStartElement(this.GetType().FullName);

            #region Area Name
            Xw.WriteStartElement(XML_Name);
            Xw.WriteString(this.Name);
            Xw.WriteEndElement();
            #endregion

            #region Area Creation Date
            Xw.WriteStartElement(XML_CreationDate);
            Xw.WriteString(this.CreationTime.ToBinary().ToString(Core.Vars.Setup.GeoSketch.NumberFormat));
            Xw.WriteEndElement();
            #endregion

            #region Area Archaeologist Name
            Xw.WriteStartElement(XML_Archaeologist);
            Xw.WriteString(this.ArchaeologistName);
            Xw.WriteEndElement();
            #endregion

            #region Area Bounds
            Xw.WriteStartElement(XML_Bounds);
            foreach (Point2D64D p in this.Bounds)
            {
                p.ToFile(Xw);
            }
            Xw.WriteEndElement();
            #endregion

            #region Area Available Layers
            Xw.WriteStartElement(XML_AvailableLayers);
            foreach (Layer l in this.AvailableLayers)
            {
                Xw.WriteStartElement(Layer.XML_Name);
                Xw.WriteString(l.Name);
                Xw.WriteEndElement();
            }
            Xw.WriteEndElement();
            #endregion

            #region Area Parent
            Xw.WriteStartElement(XML_ParentExcavationArea);
            
            {
                string ParentName = "";
                if (this.ParentExcavationArea != null)
                {
                    ParentName = this.ParentExcavationArea.Name;
                }
                Xw.WriteString(ParentName);
            }
            Xw.WriteEndElement();
            #endregion

            #region SoilSheet
            this.SoilSheet.ToFile(Xw);
            #endregion

            #region Children Excavation Areas
            Xw.WriteStartElement(XML_ChildrenExcavationAreas);
            foreach (ExcavationArea a in this.ChildrenExcavationAreas)
            {
                a.ToFile(Xw);
            }
            Xw.WriteEndElement();
            #endregion

            Xw.WriteEndElement(); 
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets the digging height of the area
        /// </summary>
        public DiggingHeight DiggingHeight
        {
            get
            {
                throw new Exception("Must be overriden");
            }
            set
            {
            }
        }

        /// <summary>
        /// Gets the treeview node of the area
        /// </summary>
        public TreeNode TreeViewNode
        {
            get
            {
                return TreeViewNodeBrowseTree(this);
            }
        }

        TreeNode TreeViewNodeBrowseTree(ExcavationArea Area)
        {
            TreeNode node = new TreeNode(Name, Vars.Setup.Forms.DrawingForm.TreeViewSiteImageIndex, Vars.Setup.Forms.DrawingForm.TreeViewSiteImageIndex);
            node.Tag = this;

            foreach (ExcavationArea a in Area.ChildrenExcavationAreas)
            {
                node.Nodes.Add(a.TreeViewNode);
            }

            return node;
        }

        /// <summary>
        /// Gets the path of the area, the path from the main excavation site
        /// </summary>
        public string Path
        {
            get
            {
                ExcavationArea current_area = this;
                string path = this.Name;

                for (int i = 0; i < this.AreaTreeDepth; i++)
                {
                    current_area = current_area.ParentExcavationArea;
                    path = current_area.Name + " - " + path;
                }

                return path;
            }
        }

        /// <summary>
        /// Gets the bounds as a polyline
        /// </summary>
        public Polyline BoundsPolyline
        {
            get
            {
                Polyline p = new Polyline(new List<Vertex>(), new Layer(), this.ParentExcavationArea);
                
                foreach (Point2D64D pt in Bounds)
                {
                    p.Vertices.Add(new Vertex(pt, VertexType.Line));
                }

                p.Vertices.Add(new Vertex(Bounds[0], VertexType.Line));

                return p;
            }
        }

        public Point2D64D Centroid
        {
            get
            {
                return Bounds.GetCentroid();
            }
        }

        public ArchaeoProject Project
        {
            get
            {
                ArchaeoProject proj = this._Project;
                ExcavationArea current_area = this;

                for (int i = 0; i < this.AreaTreeDepth; i++)
                {
                    current_area = current_area.ParentExcavationArea;
                    proj = current_area._Project;
                }

                return proj;
            }
        }

        public ExcavationArea ParentExcavationArea
        {
            get
            {
                return _ParentExcavationArea;
            }
            set
            {
                if (_ParentExcavationArea != null) { _ParentExcavationArea.ChildrenExcavationAreas.Remove(this); }
                if (value != null) { value.ChildrenExcavationAreas.Add(this); }
                _ParentExcavationArea = value;
            }
        }

        public int AreaTreeDepth
        {
            get
            {
                int depth = 0;
                ExcavationArea current_area = this;

                while ((current_area=current_area.ParentExcavationArea)!=null)
                {
                    depth++;
                }

                return depth;
            }
        }

        /// <summary>
        /// Gets or sets the soil description sheet
        /// </summary>
        public BaseSoilSheet SoilSheet
        {
            get
            {
                return _SoilSheet;
            }
            set
            {
                _SoilSheet = value;
                _SoilSheet.Area = this;
            }
        }

        /// <summary>
        /// Gets or sets survey logbook
        /// </summary>
        public SurveyLogBook SurveyLogBook
        {
            get
            {
                return _SurveyLogBook;
            }
            set
            {
                _SurveyLogBook = value;
                _SurveyLogBook.Area = this;
            }
        }
        #endregion
    }
}
